{***************************************************************
    SpyGestion  Copyright (C) 2008 Christophe KOALI <nature2gas@gmail.com>
    
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
****************************************************************}
unit modStatsPlayers;

interface

uses Classes, SysUtils,
     ESBDates,
     sFonctionsChaines;



const
   STATS_TYPE_ALL       = 1;
   STATS_TYPE_FLOTTES   = 2;
   STATS_TYPE_RECHERCHE = 3;


type
   // Pour l'alliance, inutile de créer une base de données, car elles sont
   // toutes affichées sur une même page

   // Record pour Stats Globale/Recherche/Flottes
   TPlayerStats = record
      Place: integer;        // unique (pas d'égalité)
      Points: integer;
      Uid   : integer;       // UID du joueur sur ogame (obtenu à partir du lien pour lui envoyer un message, on repère ainsi facilement les changements de pseudo)
      Nom: string;           // unique
      Alliance: string;
   end;


   PPlayerStats = ^TPlayerStats;


   TPlayerClassement = record
      Place : integer;
      Points: integer;
   end;

   TPlayerStatsAllX = record
       Total : TPlayerClassement;
       Flottes: TPlayerClassement;
       Recherche: TPlayerClassement;

       Nom     : string;      // unique
       Alliance: string;
   end;

   PPlayerStatsAllX = ^TPlayerStatsAllX;


   TListStatsPlayerSP = class(TList)
   private

   public
       procedure ImportCopyPointeur(List: TListStatsPlayerSP);
       function Ajout(Element: TPlayerStats): PPlayerStats;
       function GetMoyenneMembre: integer;
       function GetBestPlayer: PPlayerStats;

       function ExistUID(uid: integer): boolean;
   end;

    TCollectionPlayerStats = class(TList)
    private
       {--------------------------------------------------}
       {Elements spécialisés de la classes}
       FFichier: string;
       FDebugProc: TsfcProcStatus;

       FMaskResult: TListStatsPlayerSP;

       // Definit l'univers
       FUnivers: integer;

       // definit la date de creation des stats (pour savoir quand elles seront perime)
       FDateCreation: int64;

       FTypeStats : integer;



       {--------------------------------------------------}
       {Gestion des pointeurs}
       // Utilisé pour la propriété Itemx
       function GetItemx(Index: integer): TPlayerStats;

       function ReadFromLigne(Ligne: string): TPlayerStats;
       procedure ReadFromLigneExt(Ligne: string);
       procedure DoDebug(const Msg: string);

       procedure Init;
    public

       property FichierSave: string read FFichier write FFichier;
       property DebugProc : TsfcProcStatus read FDebugProc write FDebugProc;

       property Univers : integer read FUnivers write FUnivers;
       property DateCreation: int64 read FDateCreation write FDateCreation;
       property TypeStats : integer read FTypeStats write FTypeStats;


       property MaskListResult: TListStatsPlayerSP read FMaskResult;
       

       {--------------------------------------------------}
       {Gestion des pointeurs}
       // *** Ajout de pointeurs
       // Renvoi le pointeur de l'élément ajouté
       function Add(Element: TPlayerStats): PPlayerStats;
      
       // Simple ajout de pointeur
       procedure SimpleAdd(Pointeur: PPlayerStats);
      
       // Renvoi le pointeur de l'élément ajouté/modifié
       // Utilise Get_Index pour vérifier si l'élément doit être mis à jour
       function Add_Update(Element: TPlayerStats): PPlayerStats;
      
      
       // *** Suppression de pointeur
       // Libère et supprime le pointeur Index
       // Renvoi True si réussit, False si hors limite ou erreur
       function Delete(Index: integer): boolean; overload;

       // Cherche Element avec Get_Index et supprime LE
       // PREMIER pointeur trouvé, renvoi True si réussit
       function Delete(Element: string): boolean; overload;

      
       // *** Effacement de la liste de pointeur
       procedure Clear; override;
      
      
       // *** Requete sur la liste de pointeur
       function Get_Index(const Element: string): integer;
       function Get_Pointeur(const Element: string): PPlayerStats;
       function Get_UID(Uid: integer): PPlayerStats;

       function Get_Item(Index: integer): TPlayerStats;
       property Itemx[Index: integer]: TPlayerStats read GetItemx;


       function GetFromMask(const Element: string): integer;
       function GetFromAlliance(const Alliance: string): integer;
       function GetFromUIDList(ListUID: TListStatsPlayerSP): integer;


       {--------------------------------------------------}
       {Procédures et fonctions spécialisées de la classes}
       // Destructeur
       destructor Destroy; override;
       constructor Create;
       
       function SaveToFile(Fichier: string): integer;
       function LoadFromFile(Fichier: string; bReadHeaderOnly: boolean = false): integer;
       //function LoadFromFileGetNextValeur(var ChaineLu: string): string;

       procedure ImportList(List: TList);
       procedure SendToDebugAllInfo;

       procedure RemoveUID(Uid: integer);

    end;

implementation

function mspGetBestPlayer(List: TList): PPlayerStats;
var i: integer;
    pe: PPlayerStats;
begin

    if List.Count = 0 then begin
       Result := nil;
       exit;
    end;

    Result := List.Items[0];

    for i := 1 to List.Count - 1 do begin
        pe := List.Items[i];

        if pe.Points > Result.Points then
           Result := pe;

    end;

end;


function mspGetMoyenneMembre(List: TList): integer;
var i: integer;
    pe: PPlayerStats;
begin
    Result := 0;

    if List.Count = 0 then exit;

    for i := 0 to List.Count - 1 do begin
        pe := List.Items[i];

        Result := Result + pe.Points;

    end;

    Result := Result div List.Count;
    
end;

function TListStatsPlayerSP.GetMoyenneMembre: integer;
begin
    Result := mspGetMoyenneMembre(Self);
end;

function TListStatsPlayerSP.GetBestPlayer: PPlayerStats;
begin
    Result := mspGetBestPlayer(Self);
end;

function TListStatsPlayerSP.ExistUID(uid: integer): boolean;
var i: integer;
    pe: PPlayerStats;
begin

    Result := false;
    if Count = 0 then exit;

    for i := 0 to Count - 1 do begin
       pe := Items[i];

       if pe.Uid = uid then begin
          Result := true;
          exit;
       end;
    end;

end;

procedure TListStatsPlayerSP.ImportCopyPointeur(List: TListStatsPlayerSP);
var i: integer;
begin

    if List.Count = 0 then exit;

    for i := 0 to List.Count - 1 do begin
        if IndexOf(List.Items[i]) < 0 then
           inherited Add(List.Items[i]);

    end;

end;

function TListStatsPlayerSP.Ajout(Element: TPlayerStats): PPlayerStats;
var pElement: PPlayerStats;
begin

    New(pElement);          // Créer l'espace mémoire requis
    pElement^ := Element;   // Recopie les données dans l'élément pointé

    try
       inherited Add(pElement);  // Ajoute le pointeur à la liste
       Result := pElement;
    except
       Result := nil;
    end;

end;


{*******************************************************************************
 ******************************************************************************* 
 **               TCollectionPlayerStats                                     **
 *******************************************************************************
 *******************************************************************************}

procedure TCollectionPlayerStats.DoDebug(const Msg: string);
begin
     if Assigned(FDebugProc) then
        FDebugProc(Msg);
        
end;



procedure TCollectionPlayerStats.SendToDebugAllInfo;
var i: integer;
    p: PPlayerStats;
begin

    if Count = 0 then begin
       DoDebug('Aucune statistique');
       exit;
    end;

    for i := 0 to Count - 1 do begin
       p := Items[i];

       DoDebug(format('%4.4d - %6d %s (%s)',  [p.Place, p.Points, p.Nom, p.Alliance]));
    end;

    DoDebug('Elements: ' + IntToStr(Count));

end;

procedure TCollectionPlayerStats.ImportList(List: TList);
var i: integer;
    p: PPlayerStats;
begin

    if List.Count = 0 then exit;

    for i := 0 to List.Count - 1 do begin
       p := List.Items[i];

       SimpleAdd(p);

       //inherited Add(p);
    end;

end;


const
   CNT_SAVE_UNIVERS = 'Univers';
   CNT_SAVE_DATECREATION = 'Date';
   CNT_SAVE_FTypeStats  = 'Type';


procedure TCollectionPlayerStats.ReadFromLigneExt(Ligne: string);
var xPos: integer;
    x: int64;
    Key, Value: string;
begin


    xPos := pos('=', Ligne);

    if xPos = 0 then exit;

    Key := copy(Ligne, 1, xPos - 1);
    Value := trim(copy(Ligne, xPos + 1, maxint));


    if SameText(Key, CNT_SAVE_UNIVERS) then begin
       try
          xPos := StrToIntDef(Value, -1);
       except
          xPos := -1;
       end;

       if xPos > 0 then
          FUnivers := xPos;


    end else if SameText(Key, CNT_SAVE_DATECREATION) then begin
       try
          x := StrToInt64(Value);
       except
          x := -1;
       end;

       if x > 5000 then
          FDateCreation := x;

    end else if SameText(Key, CNT_SAVE_FTypeStats) then begin
       try
          xPos := StrToIntDef(Value, -1);
       except
          xPos := -1;
       end;

       if (xPos > 0) then
          FTypeStats := xPos;
          



    end;



end;


function TCollectionPlayerStats.ReadFromLigne(Ligne: string): TPlayerStats;
var xPos: integer;
    s: string;
begin
//         s := format('%d=%d,%s', [pElement.Place, pElement.Points, pElement.Nom]);


    Result.Place := -1;
    Result.Points := -1;
    Result.Nom := '';
    Result.Alliance := '';
    Result.Uid := -2;

    // Place
    xPos := pos('=', Ligne);
    if xPos = 0 then exit;

    s := copy(Ligne, 1, xPos - 1);

    Result.Place := StrToIntDef(s, -1);
    if Result.Place < 0 then exit;

    System.Delete(Ligne, 1, xPos);


    // Points
    xPos := pos(',', Ligne);
    if xPos = 0 then exit;

    s := copy(Ligne, 1, xPos - 1);


    Result.Points := StrToIntDef(s, -1);
    if Result.Points < 0 then exit;

    System.Delete(Ligne, 1, xPos);

    
    // Pseudo
    xPos := pos(chr(255), Ligne);
    if xPos = 0 then exit;

    Result.Nom := copy(Ligne, 1, xPos - 1);
    System.Delete(Ligne, 1, xPos);

    xPos := pos(',', Ligne);

    if xPos = 0 then begin
       Result.Uid := StrToIntDef(Ligne, -1);

    end else begin
       Result.Uid := StrToIntDef(copy(Ligne, 1, xPos - 1), -1);
       Result.Alliance := copy(Ligne, xPos + 1, maxint);
    end;


end;



function TCollectionPlayerStats.LoadFromFile(Fichier: string; bReadHeaderOnly: boolean = false): integer;
var Element: TPlayerStats;
    F: TextFile;
    Ligne: string;
    xPos: integer;
begin


    if Fichier = '' then Fichier := FFIchier;
    
    // ATTENTION: Les éléments sont effacés avant le chargement, à modifier en fonction de ce que l'on veut
    Clear;

    Init;

    Result  := 0;
    Fichier := sfcUnixPathToDosPath(Fichier);
    FFichier := Fichier;
    if FileExists(Fichier) = false then exit;
    
    
    AssignFile(F, FFichier);
    

    {$I-}
      ReSet(F);
    {$I+}

    // IOResult renvoie 0 => cela signifie que la dernière opération d'E/S a réussi
    if NoT(IOResult = 0) then begin
       Result := -1;
       exit;
    end;
    
    While NoT(EoF(F)) do begin
         // Lit une ligne
         ReadLn(F, Ligne);

         Ligne := trim(Ligne);
         if Ligne = '' then Continue;


         Element := ReadFromLigne(Ligne);

         if (Element.Place > 0) and (Element.Points >= 0) and (Element.Nom <> '') and (Element.Uid >= 0) then begin
            Add(Element);

         end else begin
            ReadFromLigneExt(Ligne);


            if bReadHeaderOnly then begin

               if (FTypeStats > 0) and (FUnivers > 0) and (FDateCreation > 2000) then begin
                  // ok on a ce qu'il faut
                  break;
                  
               end;


            end;

         end;

    end;
    
    // Fermeture du fichier
    CloseFile(F);

    Result := 1;
    
end;


function TCollectionPlayerStats.SaveToFile(Fichier: string): integer;
var i: integer;
    F: TextFile;
    pElement: PPlayerStats;
    s: string;
begin

    Result := -2;

    if Fichier = '' then Fichier := FFichier;
    
    AssignFile(F, Fichier);
    
    {$I-}
    ReWrite(F);
    {$I+}

    // IOResult renvoie 0 => cela signifie que la dernière opération d'E/S a réussi
    if NoT(IOResult = 0) then begin
       Result := -1;
       exit;
    end;


    s := format('%s=%d', [CNT_SAVE_UNIVERS, FUnivers]);
    WriteLn(F, s);

    if FDateCreation < 5200 then begin
       FDateCreation := DateTimeToUnix(Now);
    end;

    s := format('%s=%d', [CNT_SAVE_DATECREATION, FDateCreation]);
    WriteLn(F, s);

    s := format('%s=%d', [CNT_SAVE_FTypeStats, FTypeStats]);
    WriteLn(F, s);





    for i := 0 to Count - 1 do begin
        pElement := Items[i];

        //
        // Ecriture ici
        //

        s := format('%d=%d,%s', [pElement.Place, pElement.Points, pElement.Nom]);

        s := s + chr(255) + IntToStr(pElement.Uid);

        if pElement.Alliance <> '' then begin
           s := s + ',' + pElement.Alliance;
        end;

        WriteLn(F, s);
    end;
    
    // Résultat
    Result := Count;
    
    // Ferme le fichier
    CloseFile(F);
    
end;

procedure TCollectionPlayerStats.Init;
begin

    // Definit l'univers
    FUnivers  := -1;

    // definit la date de creation des stats (pour savoir quand elles seront perime)
    FDateCreation := 0;

    FTypeStats := -1;

end;


constructor TCollectionPlayerStats.Create;
begin

    // Héritage
    InHerited Create;

    // Code pour l'initialisation ici
    FMaskResult := TListStatsPlayerSP.Create;


    Init;
end;

destructor TCollectionPlayerStats.Destroy;
begin
    // Implémenter le code avant l'héritage de la méthode
    // C'est à dire ici
    
   
    // On appelle NOTRE clear qui lui-même appelle NOTRE Delete qui libère l'espace mémoire
    // avant d'enlever le pointeur de la TList
    // L'héritage de Destroy appelle Clear, mais es-ce le notre ou celui de la classe de base ?
    Clear;

    FMaskResult.Free;
    
    // Héritage
    InHerited Destroy;
end;


procedure TCollectionPlayerStats.Clear;
var i: integer;
begin

    if Count > 0 then begin
        For i := Count -1 downto 0 do
            Delete(i);
    end;
    
    inherited Clear;
end;


function TCollectionPlayerStats.Get_Pointeur(const Element: string): PPlayerStats;
var i: integer;
begin

   Result := nil;
   
   i := Get_Index(Element);
   if i < 0 then exit;
   Result := Items[i];

end;


function TCollectionPlayerStats.GetFromUIDList(ListUID: TListStatsPlayerSP): integer;
var i: integer;
    pe: PPlayerStats;
begin
    // la liste ListUID est plus petite que celle que  l'on parcours
    // donc on parcours chaque élément et on vérifie si il y est dans ListUID

    Result := 0;
    if Count = 0 then exit;

    for i := 0 to Count - 1 do begin
        pe := Items[i];
        
        if ListUID.ExistUID(pe.uid) then begin
           if FMaskResult.IndexOf(pe) < 0 then
              FMaskResult.Add(pe);
        end;
    end;

    Result := FMaskResult.Count;
end;

function TCollectionPlayerStats.GetFromAlliance(const Alliance: string): integer;
var i: integer;
    pe: PPlayerStats;
begin

   Result := 0;
   FMaskResult.Clear;

   if Count = 0 then exit;

   for i := 0 to Count - 1 do begin
       pe := Items[i];

       if SameText(pe.Alliance, Alliance) then begin
          FMaskResult.Add(pe);
       end;
   end;

   Result := FMaskResult.Count;

end;

function TCollectionPlayerStats.GetFromMask(const Element: string): integer;
var i: integer;
    pe: PPlayerStats;
begin

   Result := 0;
   FMaskResult.Clear;

   if Count = 0 then exit;

   for i := 0 to Count - 1 do begin
       pe := Items[i];

       if sfcMatchMaskMisc(pe.Nom, Element) then begin
          FMaskResult.Add(pe);

          if FMaskResult.Count > 15 then break;
       end;

   end;

   Result := FMaskResult.Count;

end;



procedure TCollectionPlayerStats.RemoveUID(uid: integer);
var i: integer;
    pe: PPlayerStats;
begin
   if Count <= 0 then exit;

   for i := Count - 1 downto 0 do begin
      pe := Items[i];

      if Uid = pe.Uid  then begin
         // c'est lui que l'on doit supprimer
         Delete(i);
      end;
   end;


end;

function TCollectionPlayerStats.Get_UID(Uid: integer): PPlayerStats;
var i: integer;
begin


   Result := nil;
   if Count <= 0 then exit;

   for i := 0 to Count - 1 do begin
      Result := Items[i];

      if Uid = Result.Uid  then begin
         exit;
      end;
   end;

   Result := nil;

end;


function TCollectionPlayerStats.Get_Index(const Element: string): integer;
var i: integer;
    pElement: PPlayerStats;
begin

   Result := -1;

   if Count <= 0 then exit;

   for i := 0 to Count - 1 do begin

      pElement := Items[i];


      if SameText(pElement^.Nom, Element) then begin
         Result := i;
         exit;
      end;
   end;


end;

function TCollectionPlayerStats.Add_Update(Element: TPlayerStats): PPlayerStats;
var i : integer;
begin
     
     Result := nil;
                                         {### ### ### ### ### ### ### ### ### ### ### ### ### ### }
     i := Get_Index(Element.Nom);        {### Neccessite adpation en fonction du type record  ### }
                                         {### ### ### ### ### ### ### ### ### ### ### ### ### ### }
     if i < 0 then begin 
        // L'élément n'existe pas, l'ajoute
        Result  := Add(Element);
     end else begin
        // L'élément existe déjà, le met à jour
        Result  := Items[i];     // Obtient le pointeur
        Result^ := Element;
     end;
     
end;




function TCollectionPlayerStats.Get_Item(Index: integer): TPlayerStats;
var pElement: PPlayerStats;
begin

     if Index < 0 then exit;
     if Index >= Count then exit;

     pElement := Items[Index];     // Obtient le pointeur
     Result := pElement^;

end;

function TCollectionPlayerStats.Delete(Index: integer): boolean;
var pElement: PPlayerStats;
begin

    Result := false;

    if (Index < 0) or (Index >= Count) then
       // Hors limite
       exit;


    // Obtient le pointeur
    pElement := Items[Index];

    if pElement <> nil then
       // Libére la mémoire créée lors de l'ajout
       Dispose(pElement);

    // Supprime le pointeur de la liste
    inherited Delete(Index);

    // Met le resultat à True
    Result := true;

end;

function TCollectionPlayerStats.Delete(Element: string): boolean;
var i : integer;
begin

    Result := false;
    i      := Get_Index(Element);

    if i >= 0 then begin
       // Element trouvé
       // Le supprime
       Delete(i);

       // Même si la suppression Echoue on renvoi True
       // Car l'élément a été trouvé
       Result := true;
    end;
end;


function TCollectionPlayerStats.Add(Element: TPlayerStats): PPlayerStats;
var pElement: PPlayerStats;
begin
    
    Result := nil;
    
    New(pElement);          // Créer l'espace mémoire requis
    pElement^ := Element;   // Recopie les données dans l'emplacement pointé
    
    try
       inherited Add(pElement);  // Ajoute le pointeur à la liste
       Result := pElement;
    except
       Result := nil;
    end;
    
    // Pour libérer la mémoire occupé par l'élément pointer:  Dispose(pElement);

end;

// Simple ajout de pointeur
procedure TCollectionPlayerStats.SimpleAdd(Pointeur: PPlayerStats);
begin
    
    inherited Add(Pointeur);  // Ajoute le pointeur à la liste
    
end;

function TCollectionPlayerStats.GetItemx(Index: integer): TPlayerStats;
var pElement: PPlayerStats;
begin

   if Count < 0 then exit;
   if Index >= Count then exit;

   pElement := Items[Index];
   Result := pElement^;

end;

end.
